using System;
using System.Collections.Generic;
using System.Text;
using PicoInterface;

namespace RapidHDL
{
    public static class IOInterface
    {
        public static PicoIOSvc RemotePicoIO = null;

        public const int OUTPUT_BLOCK_WORDS_MAX = 4;
        public const int INPUT_BLOCK_WORDS_MAX = OUTPUT_BLOCK_WORDS_MAX;

        const int REQUEST_SLOT_SIZE = OUTPUT_BLOCK_WORDS_MAX * 4;
        //warning, above is limited by bit width of 6 bits

        const int INPUT_BUFFER_LEN = INPUT_BLOCK_WORDS_MAX * 3 * 2;
        const int OUTPUT_BUFFER_LEN = OUTPUT_BLOCK_WORDS_MAX * 4 + 10;

        static RequestSlots RequestSlots = new RequestSlots(REQUEST_SLOT_SIZE);
        static int [] iaUpdateSlots = new int[REQUEST_SLOT_SIZE];
        static ushort[] uaInputBuffer = new ushort[INPUT_BUFFER_LEN];
        static ushort[] uaTempInputBuffer = new ushort[INPUT_BUFFER_LEN];
        static ushort[] uaOutputBuffer = new ushort[OUTPUT_BUFFER_LEN];
        static int iInputBufferIdx = 0;
        static int iOutputBufferIdx = 0;
        static int iStatusReg;
        static PacketBlock oInputPackets = new PacketBlock(INPUT_BLOCK_WORDS_MAX * 3);
        static PacketBlock oNonMemoryPackets = new PacketBlock(INPUT_BLOCK_WORDS_MAX * 3);
        static PacketBlock oOutputPackets = new PacketBlock(OUTPUT_BUFFER_LEN);


        public delegate void PacketBlockHandler(PacketBlock poPacketBlock);
        public static event PacketBlockHandler PacketBlockReceived;

        static List<MemoryBlock> MemoryBlocksNotReady = new List<MemoryBlock>();

        public static PacketBlock PacketsIn
        {
            get { return oNonMemoryPackets; }
        }

        public static bool ProcessOutputPacketBlock(ref PacketBlock poPacketBlock)
        {
            ushort [] asOutput = poPacketBlock.ToArray();
            iOutputBufferIdx = 0;
            for(int iIdx = 0; iIdx < asOutput.Length; iIdx++)
            {
                uaOutputBuffer[iOutputBufferIdx] = asOutput[iIdx];
                iOutputBufferIdx++;
            }
            return true;
        }

        public static bool PostMemory(ref MemoryBlock poMemoryBlock)
        {
            const int MAX_RETRY = 1;
            int iRetryCount = 0;

            while (iRetryCount < MAX_RETRY)
            {
                PostMemoryAsync(ref poMemoryBlock);                   

                if (!poMemoryBlock.Invalidated && poMemoryBlock.IsReady)
                    return true;

                if (!poMemoryBlock.Invalidated)
                    InputTransaction(0);

                if (!poMemoryBlock.Invalidated && poMemoryBlock.IsReady)
                    return true;

                if (MemoryBlocksNotReady.Contains(poMemoryBlock))
                    MemoryBlocksNotReady.Remove(poMemoryBlock);

                iRetryCount++;
    
                if (iRetryCount < MAX_RETRY) 
                   poMemoryBlock.Retry();
            }

            /*
            Timer oTimer = new Timer();
            oTimer.Start();
            oTimer.Stop();
            System.Windows.Forms.MessageBox.Show(oTimer.Duration.ToString());
            */
            return false;
        }

        private static bool PostMemoryAsync(ref MemoryBlock poMemoryBlock)
        {
            if (!InputTransaction(0))
                return false;

            poMemoryBlock.ToPacketBlock(ref RequestSlots,ref oOutputPackets);
            ProcessOutputPacketBlock(ref oOutputPackets);
            if (!poMemoryBlock.IsReady)
                MemoryBlocksNotReady.Add(poMemoryBlock);
            if (!OutputTransaction())
                return false;

            if (!InputTransaction(0))
                return false;
            return true;
        }        

        public static bool OutputTransaction()
        {
            GetStatus();
            if (OutputFull)
                return false;

            if (iOutputBufferIdx == 0)
                return true;

            if (RemotePicoIO == null) 
                PicoIO.WriteArray(uaOutputBuffer, iOutputBufferIdx);
            else
                RemotePicoIO.WriteArray(uaOutputBuffer, iOutputBufferIdx);

            iOutputBufferIdx = 0;
            return true;
        }

        private static void ProcessInputPacketBlock(PacketBlock poPacketBlock)
        {
            Packet oPacket;
            //oNonMemoryPackets.Reset();
            int iUpdateSlotIdx=0;

            for (int iIdx = 0; iIdx < poPacketBlock.PacketCount; iIdx++)
            {
                oPacket = poPacketBlock.Packets[iIdx];
                if (!oPacket.CompareRoute(0x00000100))  // not from memory
                {
                    uint iRoute = (uint)oPacket.Route >> 2;
                    oNonMemoryPackets.AddPacket(iRoute, oPacket.Data);
                    //Console.WriteLine("Input Packet: " + oPacket.Route.ToString() + " : " + oPacket.Data.ToString());
                }
                else
                {
                    int iSlot = (int)((oPacket.Route & 0x000000FC) >> 2); 
                    if (!RequestSlots.FulfillSlot(iSlot, (int)oPacket.Data))
                    {
                        // some kind of error here
                    }
                    else
                    {
                        iaUpdateSlots[iUpdateSlotIdx] = iSlot;
                        iUpdateSlotIdx++;
                    }
                }
            }

            if (iUpdateSlotIdx == 0)
                return;

            for (int iMemoryIdx = MemoryBlocksNotReady.Count-1; iMemoryIdx >= 0; iMemoryIdx--)
            {
                MemoryBlock oMemoryBlock = MemoryBlocksNotReady[iMemoryIdx];
                oMemoryBlock.CheckForMemoryUpdates(ref RequestSlots, ref iaUpdateSlots, iUpdateSlotIdx);
                if (oMemoryBlock.IsReady)
                    MemoryBlocksNotReady.RemoveAt(iMemoryIdx);
            }

        }

        public static bool InputTransaction(int piRecursionDepth)
        {
            GetStatus();
            if (InputEmpty)
                return true;

            if (RemotePicoIO == null)
                PicoIO.ReadArray(uaTempInputBuffer, INPUT_BUFFER_LEN);
            else
                RemotePicoIO.ReadArray(uaTempInputBuffer, INPUT_BUFFER_LEN);

            //Console.WriteLine("-------------------");
            for (int iIdx = 0; iIdx < INPUT_BUFFER_LEN; iIdx++)
            {
                //Console.WriteLine("IN -> " + uaTempInputBuffer[iIdx].ToString());

                uaInputBuffer[iInputBufferIdx] = uaTempInputBuffer[iIdx];
                iInputBufferIdx++;
            }
            //Console.WriteLine("----*************----");
            int iLeftOver = oInputPackets.FromArray(ref uaInputBuffer, iInputBufferIdx);
            if (iLeftOver > 0)
            {
                iInputBufferIdx = 0;
                for (int iIdx = iLeftOver; iIdx < INPUT_BUFFER_LEN; iIdx++)
                {
                    if (iInputBufferIdx <= iIdx)
                        uaInputBuffer[iInputBufferIdx] = 0;
                    else
                        uaInputBuffer[iIdx] = uaInputBuffer[iInputBufferIdx];
                    iInputBufferIdx++;
                }
            }
            iInputBufferIdx = 0;

            ProcessInputPacketBlock(oInputPackets);

            GetStatus();
            bool bEmpty = InputEmpty;

            if (piRecursionDepth > 1)
                return false;
            if (bEmpty)
                return true;
            InputTransaction(piRecursionDepth + 1);
            return true;
        }

        private static void GetStatus()
        {
            if (RemotePicoIO == null)
                iStatusReg = PicoIO.ReadIOStatusReg();
            else
                iStatusReg = RemotePicoIO.ReadIOStatusReg();
        }

        public static bool OutputFull
        {
            get
            {
                return ((iStatusReg & 0x0002) != 0);
            }
        }

        public static bool InputEmpty
        {
            get
            {
                return ((iStatusReg & 0x0001) != 0);
            }
        }

        public static void CheckFPGA()
        {
            InputTransaction(0);
        }

        public static bool PostFPGA(FPGABlock poFPGABlock)
        {
            Dictionary<int, Dictionary<int, FPGAEntry>> dicSplit = poFPGABlock.Split();
            if (dicSplit.Count == 0)
                return true;

            foreach (int iBlock in dicSplit.Keys)
            {
                oOutputPackets.Reset();
                if (!InputTransaction(0))
                    return false;

                Dictionary<int, FPGAEntry> dicBlock = dicSplit[iBlock];
                foreach (int iEntry in dicBlock.Keys)
                {
                    FPGAEntry oEntry = dicBlock[iEntry];

                    uint iHeader = 0x80000000;
                    uint iSlot = ((uint)oEntry.Route << 24) & (uint)0x3F000000;
                    uint iAddress = (uint)oEntry.Address & (uint)0x00FFFFFF;
                    if (!oEntry.WriteBit)
                        iHeader = 0x00000000;

                    uint iRoute = iHeader | iSlot | iAddress;
                    uint iData = oEntry.Data;

                    oOutputPackets.AddPacket(iRoute, iData);
                }
                ProcessOutputPacketBlock(ref oOutputPackets);
                if (!OutputTransaction())
                    if (!OutputTransaction())
                        return false;
                oOutputPackets.Reset();
                if (!InputTransaction(0))
                    return false;
                //System.Windows.Forms.MessageBox.Show("WAIT");
                //return true;
            }
            return true;
        }


    }
}
